﻿// License Rider: I really don't care how you use this code, or if you give credit. Just don't blame me for any damage you do
using System;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Automation;
using System.Windows.Forms;
using ProdSpy.Core;
using ProdSpy.Properties;
using Point = System.Windows.Point;

namespace ProdSpy
{
    /// <summary>
    /// Form to spy on windows to get Prod Information
    /// </summary>
    public partial class FormMain : Form
    {
        //Todo: extract to settings
        private const int TIMER_DELAY = 500;
        private const int COLLAPSED_SPLITTER_WIDTH = 344;
        private const int EXPANDED_SPLITTER_WIDTH = 544;
        private const int EXPANDED_SPLITTER_DIST = 218;

        /// <summary>
        /// Initializes the main form for the ProdUI Spy
        /// </summary>
        public FormMain()
        {
            InitializeComponent();
        }

        #region Variables

        private Timer _cursorTimer;
        private bool _isDown;

        private IntPtr _focusedApplicationHandle;
        private TreeNode _CurrentWindowRoot;
        private IntPtr _previousFocusedApplicationHandle = IntPtr.Zero;

        private AutomationElement _focusedElement;

        #endregion Variables

        /// <summary>
        /// Handles the Load event of the FormMain control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void FormMain_Load(object sender, EventArgs e)
        {
            InitBrowseIcon();
            SetGraphState();

            /* Set up the eye timer */
            _cursorTimer = new Timer();
            _cursorTimer.Tick += CursorTimer_Tick;
            _cursorTimer.Interval = TIMER_DELAY;
        }

        /// <summary>
        /// Using this to make sure all focus graphics are obliterated
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.FormClosingEventArgs"/> instance containing the event data.</param>
        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (_focusedApplicationHandle == IntPtr.Zero) return;
            NativeMethods.InvalidateRect(_focusedApplicationHandle, IntPtr.Zero, true);
            NativeMethods.UpdateWindow(_focusedApplicationHandle);
        }

        private void InitBrowseIcon()
        {
            /* Browse Icon */
            Icon browseIcon = Icon.FromHandle(Cursors.SizeAll.Handle);
            LblCap.Image = browseIcon.ToBitmap();
            NativeMethods.DestroyIcon(browseIcon.Handle);
        }

        private void SetGraphState()
        {
            /* settings */
            GraphSplitter.Panel1Collapsed = Settings.Default.CollapseGraph;
            if (GraphSplitter.Panel1Collapsed)
            {
                Width = COLLAPSED_SPLITTER_WIDTH;
            }
            else
            {
                Width = EXPANDED_SPLITTER_WIDTH;
                GraphSplitter.SplitterDistance = EXPANDED_SPLITTER_DIST;
            }
        }


        #region Main Spy Loop

        /// <summary>
        /// Begins the evaluation of windows
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
        private void LblCap_MouseDown(object sender, MouseEventArgs e)
        {
            _isDown = true;
            _cursorTimer.Start();
            SetState(true);

            LblCap.Visible = false;
            LblBegin.Enabled = false;

            TsGraph.Enabled = true;
            TvGraph.Nodes.Clear();
        }

        /// <summary>
        /// Ends the evaluation of windows
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
        private void LblCap_MouseUp(object sender, MouseEventArgs e)
        {
            _isDown = false;
            _cursorTimer.Stop();

            SetState(false);

            LblCap.Visible = true;
            LblBegin.Enabled = true;
            TvGraph.Nodes.Add(_CurrentWindowRoot);
            _previousFocusedApplicationHandle = IntPtr.Zero;
        }

        /// <summary>
        /// Handles checking window under cursor location
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void CursorTimer_Tick(object sender, EventArgs e)
        {
            /* Cursor location */
            NativeMethods.POINT p;

            /* Are we still in a capture mode? */
            if (!_isDown)
            {
                return;
            }

            if (!NativeMethods.GetCursorPos(out p))
            {
                return;
            }

            TsCoords.Text = p.X + @", " + p.Y;
            Point cursorPoint = new Point(p.X, p.Y);

            /* control under cursor */
            _focusedElement = AutomationElement.FromPoint(cursorPoint);

            /* Get the handle to the main application of the control under focus */
            GetApplicationHandle();

            /* Make sure we're not examining ourselves */
            if (!VerifyContinue(_focusedApplicationHandle))
            {
                return;
            }

            /* See if this is a completely new or different window */
            CheckNewWindow();

            /* Get all of the (possibly different) focused controls specifics */
            GetElement(_focusedElement);

            /* reassign */
            _previousFocusedApplicationHandle = _focusedApplicationHandle;
        }

        /// <summary>
        /// Gets the application handle of the focused window.
        /// </summary>
        private void GetApplicationHandle()
        {
            /* Get the main windows handle via the PID */
            int pid = _focusedElement.Current.ProcessId;
            Process pro = Process.GetProcessById(pid);
            _focusedApplicationHandle = pro.MainWindowHandle;
        }

        /// <summary>
        /// Checks if this is a completely new top level form, and if so, calls for control enumeration
        /// </summary>
        private void CheckNewWindow()
        {
            /* If this is a completely new top level form, reload the control tree */
            if (_previousFocusedApplicationHandle == _focusedApplicationHandle)
            {
                return;
            }

            AutomationElement root = AutomationElement.FromHandle(_focusedApplicationHandle);
            _CurrentWindowRoot = new TreeNode(root.Current.Name);
            WalkControlElements(root, _CurrentWindowRoot);
        }

        /// <summary>
        /// Checks to see if cursor is inside this form
        /// </summary>
        /// <param name="thePHwnd">The handle to current ProdSpy instance.</param>
        /// <returns>
        ///   <c>true</c> if we should continue <c>false</c> if we're inside this window
        /// </returns>
        private bool VerifyContinue(IntPtr thePHwnd)
        {
            if (Handle != thePHwnd && thePHwnd != IntPtr.Zero)
            {
                return true;
            }

            return false;
        }

        #endregion

        #region Toolbar

        /// <summary>
        /// Loads control graph form
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void TsGraph_Click(object sender, EventArgs e)
        {
            if (GraphSplitter.Panel1Collapsed)
            {
                GraphSplitter.Panel1Collapsed = false;
                Width = 544;
                GraphSplitter.SplitterDistance = 218;
                return;
            }
            GraphSplitter.Panel1Collapsed = true;
            Width = 344;
        }

        /// <summary>
        /// Sets the forms "Topmost" property
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void TsPin_Click(object sender, EventArgs e)
        {
            if (TopMost)
            {
                TopMost = false;
                return;
            }
            TopMost = true;
        }

        /// <summary>
        /// Handles the Click event of the TsOptions control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void TsOptions_Click(object sender, EventArgs e)
        {
            FormOptions frmOptions = new FormOptions();
            frmOptions.ShowDialog();
        }

        #endregion Toolbar

        #region Drawing

        /// <summary>
        /// Determines if are we in capture mode
        /// </summary>
        /// <param name="isMouseDown">If mouse is down (capture mode) or not</param>
        public static void SetState(bool isMouseDown)
        {
            if (isMouseDown)
            {
                Cursor.Current = Cursors.SizeAll;
                return;
            }
            Cursor.Current = Cursors.Arrow;
            NativeMethods.InvalidateRect(IntPtr.Zero, IntPtr.Zero, true);
        }

        private static Rectangle PointToRectangle(AutomationElement focus, ref NativeMethods.POINT p)
        {
            Rectangle r = new Rectangle();
            r.X = p.X;
            r.Y = p.Y;
            r.Width = (int)focus.Current.BoundingRectangle.Width;
            r.Height = (int)focus.Current.BoundingRectangle.Height;
            return r;
        }

        private void PaintTargetBox(AutomationElement focus)
        {
            if (focus == null) return;
            //Process proc = Process.GetProcessById(focus.Current.ProcessId);
            Graphics test = Graphics.FromHwnd(_focusedApplicationHandle);


            NativeMethods.POINT p = new NativeMethods.POINT((int)focus.Current.BoundingRectangle.X, (int)focus.Current.BoundingRectangle.Y);
            NativeMethods.ScreenToClient(_focusedApplicationHandle, ref p);
            Rectangle r = PointToRectangle(focus, ref p);

            NativeMethods.InvalidateRect(_focusedApplicationHandle, IntPtr.Zero, true);
            NativeMethods.UpdateWindow(_focusedApplicationHandle);

            Pen highlightPen = new Pen(Settings.Default.HighlightColor, 3);
            test.DrawRectangle(highlightPen, r);
        }

        #endregion

        #region Control Info

        /// <summary>
        /// Walks the elements in the target windows control tree.
        /// </summary>
        /// <param name="rootElement">The root element.</param>
        /// <param name="treeNode">The tree node.</param>
        private void WalkControlElements(AutomationElement rootElement, TreeNode treeNode)
        {
            AutomationElement elementNode = TreeWalker.ControlViewWalker.GetFirstChild(rootElement);

            while (elementNode != null)
            {
                TreeNode childTreeNode = new TreeNode(elementNode.Current.ControlType.LocalizedControlType + ": " + elementNode.Current.Name);
                childTreeNode.Tag = elementNode;
                treeNode.Nodes.Add(childTreeNode);
                WalkControlElements(elementNode, childTreeNode);

                elementNode = TreeWalker.ControlViewWalker.GetNextSibling(elementNode);
            }
        }

        private void GetElement(AutomationElement element)
        {
            foreach (TreeNode node in _CurrentWindowRoot.Nodes)
            {
                string cur = element.GetRuntimeId()[0].ToString() + element.GetRuntimeId()[1].ToString();

                AutomationElement tempElement = (AutomationElement)node.Tag;
                string tempId = tempElement.GetRuntimeId()[0].ToString() + tempElement.GetRuntimeId()[1].ToString();
                if (cur.CompareTo(tempId) != 0) continue;

                PaintTargetBox(element);
                FillPatterns(element);
                FillProperties(element);
            }
        }

        private void FillPatterns(AutomationElement currentElement)
        {
            if (currentElement == null) return;
            LvActions.Items.Clear();
            foreach (AutomationPattern pattern in currentElement.GetSupportedPatterns())
            {
                foreach (string action in PatternCrosswalk.Translate(pattern))
                {
                    ListViewItem it = new ListViewItem(action, 0);
                    LvActions.Items.Add(it);
                }
            }
        }

        /// <summary>
        /// Fills the focused elements properties.
        /// </summary>
        /// <param name="currentElement">The current element.</param>
        private void FillProperties(AutomationElement currentElement)
        {
            if (currentElement == null) return;
            LvProperties.Items.Clear();

            foreach (AutomationProperty property in currentElement.GetSupportedProperties())
            {
                string prop = property.ProgrammaticName;
                string cleanProp = prop.Substring(prop.IndexOf(".") + 1);
                string value = string.Empty;
                ListViewItem item;
                try
                {
                    if (currentElement.GetCurrentPropertyValue(property).GetType() == typeof(ControlType))
                    {
                        ControlType y = (ControlType)currentElement.GetCurrentPropertyValue(property);
                        item = new ListViewItem(cleanProp.Replace("Property", string.Empty));
                        item.SubItems.Add(y.LocalizedControlType);
                    }
                    else
                    {
                        item = new ListViewItem(cleanProp.Replace("Property", string.Empty));
                        item.SubItems.Add(currentElement.GetCurrentPropertyValue(property).ToString());
                    }
                    LvProperties.Items.Add(item);
                }
                catch
                {
                    continue;
                }

                LvProperties.Sorting = SortOrder.Ascending;
                LvProperties.Sort();
            }


        }

        #endregion Control Info


        /// <summary>
        /// Handles the SelectionChanged event of the RtbCode control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void RtbCode_SelectionChanged(object sender, EventArgs e)
        {
            CtxCopy.Enabled = RtbCode.SelectedText.Length > 0;
        }

        /// <summary>
        /// Handles the Click event of the CtxCopy control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void CtxCopy_Click(object sender, EventArgs e)
        {
            Clipboard.SetText(RtbCode.SelectedText);
        }

        private void TvGraph_AfterSelect(object sender, TreeViewEventArgs e)
        {
            AutomationElement showElement = (AutomationElement)e.Node.Tag;
            PaintTargetBox(showElement);
            FillPatterns(showElement);
            FillProperties(showElement);
        }

        private void CtxTextReport_Click(object sender, EventArgs e)
        {

        }

    }
}